package com.nys.conci.base;


import com.nys.conci.base.utilitario.logger.LoggerUtil;
import java.io.Serializable;
import java.sql.Connection;
import java.util.List;
import javax.naming.InitialContext;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.sql.DataSource;


public abstract class ServicioBase {

    /**
     * EntityManager principal
     */
    @PersistenceContext(unitName = "NYSoftPU")
    protected EntityManager em;

    /**
     * Crea un nuevo registro en la base de datos utilizando la entidad
     * solicitada
     *
     * @param g Object
     * @throws RuntimeException
     */
    public void guardar(Object g) {
        em.persist(g);
    }   //guardar

    /**
     * Actualiza un registro ya existente en la base de datos utilizando la
     * entidad solicitada
     *
     * @param g Object
     * @throws RuntimeException
     */
    public void actualizar(Object g) {
        try {
            em.merge(g);

        } catch (Exception e) {
            if (e.getCause().getMessage().equals("javax.persistence.OptimisticLockException")) {
                throw new RuntimeException("El registro que intenta modificar ha sido modificado", e);
            } else {
                throw new RuntimeException("Error al editar el objecto " + g.getClass(), e);
            }
        }
    }   //guardar

    /**
     * Elimina un registro ya existente en la base de datos utilizando la
     * entidad solicitada
     *
     * @param g Object
     * @throws RuntimeException
     */
    public void eliminar(Object g) {
        try {
            em.remove(em.merge(g));
        } catch (Exception e) {
            throw new RuntimeException("Error al eliminar el objeto " + g.getClass(), e);
        }
    }   //eliminar

    /**
     * Elimina un registro ya existente en la base de datos utilizando la
     * entidad solicitada
     *
     * @param g Object
     * @throws RuntimeException
     */
    public void eliminarFlush(Object g) {
        try {
            em.remove(em.merge(g));
            em.flush();
        } catch (Exception e) {
            throw new RuntimeException("Error al eliminar el objeto " + g.getClass(), e);
        }
    }   //eliminar

    /**
     * Metodo encargado de buscar en la capa de datos, por el registro de la
     * clase envia como parametro, que tenga su identificador igual a parametro
     * pk.
     *
     * @param <T> Parametro para hacer la funcion generica
     * @param clase Clase a encontrar
     * @param pk Llave primaria del objeto a encontrar
     * @return Objeto encontrado
     */
    public <T> T encontrar(Class<T> clase, Serializable pk) {
        return em.find(clase, pk);
    }

    /**
     *
     * @param <T> Parametro para hacer la funcion generica
     * @param clase Clase a encontrar
     * @param pk Llave primaria del objeto a encontrar
     * @return Refe encontrado
     */
    public <T> T obtenerReferencia(Class<T> clase, Serializable pk) {
        return em.getReference(clase, pk);
    }

    /**
     * Realiza un listado de TODAS las entidades solicitadas
     *
     * @param c Class<T>
     * @param <T> T
     * @return List[G]
     * @throws RuntimeException
     */
    public <T> List<T> listar(Class<T> c) {

        try {
            Query q = em.createQuery("select object(o) from " + c.getSimpleName() + " as o");
            return q.getResultList();
        } catch (Exception e) {
            throw new RuntimeException("Error al listar el objeto " + c.getSimpleName(), e);
        }
    }   //listar

    /**
     * Ejecuta un namedquery en particular, utilizando los parámetros enviados.
     * El formato de las variables de namedquery debe ser un signo de
     * interrogación: ? Ej. select o from Contacto o where nombre = ? Se puede
     * usar NamedQuery o NamedNativeQuery Este método también puede ejecutar
     * procedimientos almacenados (utilizando un NamedNativeQuery donde se haga
     * el llamado)
     *
     * @param namedquery
     * @param param ... Object
     * @return List[G]
     */
    public List<Object> ejecutarNamedQuery(String namedquery, Object... param) {

        try {
            Query q = em.createNamedQuery(namedquery);
            int i = 1;
            for (Object a : param) {
                q.setParameter(i, a);
                i++;
            }
            return q.getResultList();
        } catch (Exception excepcion) {
            throw new RuntimeException("Error al ejecutar namedQuery: " + namedquery, excepcion);
        }
    }   //ejecutarNamedQuery

    /**
     * Ejecuta un namedquery en particular, utilizando los parámetros enviados.
     * El formato de las variables de namedquery debe ser un signo de
     * interrogación: ? Ej. select o from Contacto o where nombre = ? Se puede
     * usar NamedQuery o NamedNativeQuery Este método también puede ejecutar
     * procedimientos almacenados (utilizando un NamedNativeQuery donde se haga
     * el llamado)
     *
     * @param nativequery String
     * @param param ... Object
     * @return List[G]
     */
    public List<Object> ejecutarNativeQuery(String nativequery, Object... param) {
        try {
            Query q = em.createNativeQuery(nativequery);
            int i = 1;
            for (Object a : param) {
                q.setParameter(i, a);
                i++;
            }
            return q.getResultList();
        } catch (Exception excepcion) {
            throw new RuntimeException("No encontré el objeto pedido", excepcion);
        }
    }   //ejecutarNamedQuery

    /**
     * Retorna una conexión.
     *
     * @return java.sql.Connection Con la conexion, solicitada al contenedor.
     */
    protected Connection retornaConexion() {
        Connection conn = null;
        try {
            InitialContext context = new InitialContext();
            DataSource source = (DataSource) context.lookup("jdbc/nysoft");
            conn = source.getConnection();
        } catch (Exception ex) {
            LoggerUtil.registrarLogError(ex, false);
        }
        return conn;
    }
    
    
    /**
     * Método encargado de retornar la conexión a la BD, utilizado en la aplicación,
     * con el Origen de Datos especializado para los reportes.
     * 
     * @return java.sql.Connection Conexión SQL.
     */
    protected Connection retornarConexionReporte(){
        return this.retornaConexion("jdbc/reporte");
    }
    
    /**
     * Método genérico para obtener una conexión del pool de conexiones de un origen 
     * de datos, por medio del JNDI de dicho origen de datos.
     * @param jndi JNDI del origen de datos
     * @return java.sql.Connection Conexión SQL encontrada
     */
    private Connection retornaConexion(String jndi){
        Connection  conn = null;
        try {
            InitialContext context = new InitialContext();
            DataSource source = (DataSource)context.lookup(jndi);
            conn = source.getConnection();
        } catch (Exception ex) {
            LoggerUtil.registrarLogError(ex, false);
        }
        return conn;
    }    
}
